package restful

import (
	"log"
	"moss/api"
	"moss/conf"
	"moss/proto"
	"moss/status"
	"net/http"
	"time"
)

type APIFunction func(w http.ResponseWriter, req *http.Request, requestId string) status.Status

func GeneralHandler(w http.ResponseWriter, req *http.Request, apiName string, f APIFunction) {
	startEpoch := time.Now()
	requestId := generateRequestId()

	var rc status.Status

	if req.Method == "POST" {
		rc = f(w, req, requestId)
		if rc != status.Success {
			reportError(w, req, rc, requestId)
		}

	} else {
		allowAnonymous := req.Method == "GET"
		redirectToWebsite := false
		rc = AuthCheck(w, req, allowAnonymous, requestId)
		if rc == status.Success {
			if allowAnonymous && isAnonymous(req.Header) {
				redirectToWebsite = true
				rc = sendWebsiteContent(w, req, requestId)
			} else {
				rc = f(w, req, requestId)
			}
		}

		if !redirectToWebsite && rc != status.Success {
			reportError(w, req, rc, requestId)
		}
	}

	processDelay := int(time.Now().Sub(startEpoch).Seconds() * 1000)
	log.Printf("ID %v, API %v, %v ms, %v\n", requestId, apiName, processDelay, status.GetCodeString(rc))

	api.WriteLogging(req, requestId, apiName, rc, getBucketName(req), getObjectName(req),
		getAccessKeyId(req), startEpoch.String(), processDelay)
}

func GetServiceHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "GetService",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			doc, rc := api.GetService(requestId, getAccessKeyId(req))
			if rc == status.Success {
				sendXmlDocument(doc, http.StatusOK, requestId, w, nil)
			}
			return rc
		})
}

func PutBucketHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "PutBucket",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			location := "us-east-1"
			var xmlDoc proto.CreateBucketConfiguration
			if req.ContentLength > 0 {
				rc := loadXmlDocument(&xmlDoc, req)
				if rc != status.Success {
					return rc
				}
				location = xmlDoc.LocationConstraint
			}

			permission := getHeaderString(req, conf.HTTPHeaderOssACL, conf.PermissionPrivate)
			rc := api.PutBucket(requestId, getAccessKeyId(req), getBucketName(req), permission, location)
			if rc == status.Success {
				sendSuccess(requestId, w, nil)
			}
			return rc
		})
}

func PutBucketAclHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "PutBucketAcl",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			permission := req.Header.Get(conf.HTTPHeaderOssACL)
			rc := api.PutBucketAcl(requestId, getAccessKeyId(req), getBucketName(req), permission)
			if rc == status.Success {
				sendSuccess(requestId, w, nil)
			}
			return rc
		})
}

func GetBucketHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "GetBucket",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			delimiter := req.URL.Query().Get("delimiter")
			marker := req.URL.Query().Get("marker")
			prefix := req.URL.Query().Get("prefix")
			maxKeys := getQueryInt(req, "max-keys", conf.Config.Limit.DefaultMaxKeys)

			xmlDoc, rc := api.GetBucket(requestId, getAccessKeyId(req), getBucketName(req), delimiter, marker, maxKeys, prefix)
			if rc == status.Success {
				sendXmlDocument(xmlDoc, http.StatusOK, requestId, w, nil)
			}
			return rc
		})
}

func GetBucketAclHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "GetBucketAcl",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			xmlDoc, rc := api.GetBucketAcl(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendXmlDocument(xmlDoc, http.StatusOK, requestId, w, map[string]string{conf.HTTPHeaderOssACL: xmlDoc.AccessControlList.Grant})
			}
			return rc
		})
}

func GetBucketLocationHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "GetBucketLocation",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			xmlDoc, rc := api.GetBucketLocation(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendXmlDocument(xmlDoc, http.StatusOK, requestId, w, map[string]string{conf.HTTPHeaderLocation: xmlDoc.Location})
			}
			return rc
		})
}

func GetBucketInfoHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "GetBucketInfo",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			bucketInfo, rc := api.GetBucketInfo(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendXmlDocument(bucketInfo, http.StatusOK, requestId, w, nil)
			}
			return rc
		})
}

func DeleteBucketHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "DeleteBucket",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			rc := api.DeleteBucket(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendNoContent(requestId, w, nil)
			}
			return rc
		})
}

func PutBucketWebsiteHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "PutBucketWebsite",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			var xmlDoc proto.WebsiteConfiguration
			rc := loadAndValidateXmlDocument(&xmlDoc, req)
			if rc != status.Success {
				return rc
			}
			rc = api.PutBucketWebsite(requestId, getAccessKeyId(req), getBucketName(req), xmlDoc)
			if rc == status.Success {
				sendSuccess(requestId, w, nil)
			}
			return rc
		})
}

func GetBucketWebsiteHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "GetBucketWebsite",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			xmlDoc, rc := api.GetBucketWebsite(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendXmlDocument(xmlDoc, http.StatusOK, requestId, w, nil)
			}
			return rc
		})
}

func DeleteBucketWebsiteHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "DeleteBucketWebsite",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			rc := api.DeleteBucketWebsite(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendNoContent(requestId, w, nil)
			}
			return rc
		})
}

func PutBucketRefererHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "PutBucketReferer",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			var xmlDoc proto.RefererConfiguration
			rc := loadAndValidateXmlDocument(&xmlDoc, req)
			if rc != status.Success {
				return rc
			}

			rc = api.PutBucketReferer(requestId, getAccessKeyId(req), getBucketName(req), xmlDoc)
			if rc == status.Success {
				sendSuccess(requestId, w, nil)
			}
			return rc
		})
}

func GetBucketRefererHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "PutBucketReferer",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			xmlDoc, rc := api.GetBucketReferer(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendXmlDocument(xmlDoc, http.StatusOK, requestId, w, nil)
			}
			return rc
		})
}

func PutBucketLoggingHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "PutBucketLogging",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			var xmlDoc proto.BucketLoggingStatus
			rc := loadAndValidateXmlDocument(&xmlDoc, req)
			if rc != status.Success {
				return rc
			}

			location := getDataCenterName(req)
			enabled := true
			targetBucket := xmlDoc.LoggingEnabled.TargetBucket
			targetPrefix := xmlDoc.LoggingEnabled.TargetPrefix

			if len(targetBucket) == 0 && len(targetPrefix) == 0 {
				enabled = false
			}

			if enabled {
				rc = api.PutBucketLogging(requestId, getAccessKeyId(req), getBucketName(req), targetBucket, targetPrefix, location)
			} else {
				rc = api.DeleteBucketLogging(requestId, getAccessKeyId(req), getBucketName(req))
			}

			if rc == status.Success {
				sendSuccess(requestId, w, nil)
			}

			return rc
		})
}

func GetBucketLoggingHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "GetBucketLogging",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			xmlDoc, rc := api.GetBucketLogging(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendXmlDocument(xmlDoc, http.StatusOK, requestId, w, nil)
			}
			return rc
		})
}

func DeleteBucketLoggingHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "DeleteBucketLogging",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			rc := api.DeleteBucketLogging(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendNoContent(requestId, w, nil)
			}
			return rc
		})
}

func PutBucketLifecycleHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "PutBucketLifecycle",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			var xmlDoc proto.LifecycleConfiguration
			rc := loadXmlDocument(&xmlDoc, req)
			if rc != status.Success {
				return rc
			}

			for _, v := range xmlDoc.Rule {
				rc = api.PutBucketLifecycle(requestId, getAccessKeyId(req), getBucketName(req), v)
				if rc != status.Success {
					return rc
				}
			}

			sendSuccess(requestId, w, nil)
			return status.Success
		})
}

func GetBucketLifecycleHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "GetBucketLifecycle",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			xmlDoc, rc := api.GetBucketLifecycle(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendXmlDocument(xmlDoc, http.StatusOK, requestId, w, nil)
			}
			return rc
		})
}

func DeleteBucketLifecycleHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "DeleteBucketLifecycle",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			rc := api.DeleteBucketLifecycle(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendNoContent(requestId, w, nil)
			}
			return rc
		})
}

func HeadBucketHandler(w http.ResponseWriter, req *http.Request) {
	GeneralHandler(w, req, "HeadBucketHandler",
		func(w http.ResponseWriter, req *http.Request, requestId string) status.Status {
			rc := api.HeadBucket(requestId, getAccessKeyId(req), getBucketName(req))
			if rc == status.Success {
				sendSuccess(requestId, w, nil)
			}
			return rc
		})
}
